/**
*	@file ResultTable.cpp
*
*	@brief this header file is defining table structure
*
*	@author li yuchen
*
*	@date 1/28/2011
*/
#include"ResultTable.h"
#include"QueryEvaulator.h"
#include"PKB.h"
#include<ppl.h>
#include<algorithm>
#include<sstream>
#include<iostream>
#include <stdarg.h>
#include <stdio.h>
using namespace std;
using namespace Concurrency;


struct CompareObject{
	vector<int>* idx_on_sort;
	bool operator() (vector<int>& tuple_a, vector<int>& tuple_b){
		for(int i = 0; i < idx_on_sort->size(); i++){
			if(tuple_a[(*idx_on_sort)[i]] < tuple_b[(*idx_on_sort)[i]]) return true;
			if(tuple_a[(*idx_on_sort)[i]] > tuple_b[(*idx_on_sort)[i]]) return false;
		}
		return false;
	}
};

// if a > b return -1
// if a == b return 0
// if a < b return 1
// for the join of two tables
int static CompareTuple(vector<int>& tuple_a, vector<int>& tuple_b, vector<int>& cols_a, vector<int>& cols_b){
	for(int i = 0; i < cols_a.size(); i++){
		if(tuple_a[cols_a[i]] < tuple_b[cols_b[i]]) return -1;
		else if(tuple_a[cols_a[i]] > tuple_b[cols_b[i]]) return 1;
	}
	return 0;
}

ResultTable::ResultTable(vector<int> query_var_idxes){
	this->query_var_idxes_ = query_var_idxes;
}

ResultTable::ResultTable(QueryVarIdx single_query_var_enum,bool enum_var){
	if(enum_var){
		this->query_var_idxes_.push_back(single_query_var_enum);
		switch(QueryVarTable::GetQueryVarType(single_query_var_enum)){
		case PROC_TYPE:
			this->table_data_.resize(ProcTable::GetSize());
			parallel_for_each(concurrency_counter.begin() + 1,concurrency_counter.begin() + 1+ProcTable::GetSize(),[&](int i){
				vector<int> new_tuple;
				new_tuple.push_back(i);
				this->table_data_[i-1].swap(new_tuple);
			});
			break;
		case VAR_TYPE:
			this->table_data_.resize(VarTable::GetSize());
			parallel_for_each(concurrency_counter.begin() + 1,concurrency_counter.begin()+1+VarTable::GetSize(),[&](int i){
				vector<int> new_tuple;
				new_tuple.push_back(i);
				this->table_data_[i-1].swap(new_tuple);
			});
			break;
		case CONST_TYPE:
			{
				this->table_data_.resize(ConstTable::GetSize());
				vector<int> all_const = ConstTable::GetConstList();
				parallel_for_each(concurrency_counter.begin(),concurrency_counter.begin()+ConstTable::GetSize(),[&](int i){
					vector<int> new_tuple;
					new_tuple.push_back(all_const[i]);
					this->table_data_[i].swap(new_tuple);
				});
				break;
			}
		case STMT_TYPE:
			this->table_data_.resize(StmtTable::GetSize());
			parallel_for_each(concurrency_counter.begin() + 1,concurrency_counter.begin()+1+StmtTable::GetSize(),[&](int i){
				vector<int> new_tuple;
				new_tuple.push_back(i);
				this->table_data_[i-1].swap(new_tuple);
			});
			break;
		case ASSIGN_TYPE:
			this->table_data_.resize(StmtTable::GetSizeAssign());
			parallel_for_each(concurrency_counter.begin() + 1,concurrency_counter.begin()+1+StmtTable::GetSizeAssign(),[&](int i){
				vector<int> new_tuple;
				new_tuple.push_back(StmtTable::GetAssignTNode(i)->stmt_num);
				this->table_data_[i-1].swap(new_tuple);
			});
			break;
		case IF_TYPE:
			this->table_data_.resize(StmtTable::GetSizeIf());
			parallel_for_each(concurrency_counter.begin() + 1,concurrency_counter.begin()+1+StmtTable::GetSizeIf(),[&](int i){
				vector<int> new_tuple;
				new_tuple.push_back(StmtTable::GetIfTNode(i)->stmt_num);
				this->table_data_[i-1].swap(new_tuple);;
			});
			break;
		case WHILE_TYPE:
			this->table_data_.resize(StmtTable::GetSizeWhile());
			parallel_for_each(concurrency_counter.begin() + 1,concurrency_counter.begin()+1+StmtTable::GetSizeWhile(),[&](int i){
				vector<int> new_tuple;
				new_tuple.push_back(StmtTable::GetWhileTNode(i)->stmt_num);
				this->table_data_[i-1].swap(new_tuple);
			});
			break;
		case CALL_TYPE:
			this->table_data_.resize(StmtTable::GetSizeCall());
			parallel_for_each(concurrency_counter.begin() + 1,concurrency_counter.begin()+1+StmtTable::GetSizeCall(),[&](int i){
				vector<int> new_tuple;
				new_tuple.push_back(StmtTable::GetCallTNode(i)->stmt_num);
				this->table_data_[i-1].swap(new_tuple);
			});
			break;
		default:
			cout << "unkown error!" << endl;
			getchar();
			exit(1);
		}
		this->sorted_idxes_.push_back(0);
	}else{
		this->query_var_idxes_.push_back(single_query_var_enum);
	}
}

ResultTable::ResultTable(QueryVarIdx query_arg_a,QueryVarIdx query_arg_b){
	this->query_var_idxes_.push_back(query_arg_a);
	this->query_var_idxes_.push_back(query_arg_b);
}

ResultTable::~ResultTable(){
	table_data_.clear();
	query_var_idxes_.clear();
}

void ResultTable::AddResultTuple(vector<int>& tuple){
	if(this->query_var_idxes_.size() != tuple.size()){
		cout << "tuple size must be equal to the size of the variable idxes size" << endl;
		getchar();
		exit(1);
	}
	table_data_.push_back(tuple);
}

// step 1: find the common and difference columns
// step 2: build the new table
// step 3: join two table and return the result
ResultTable* ResultTable::NaiveJoinResultTable(ResultTable* table_b){
	// store the information of the common colomns from each table
	vector<int> common_cols_a;
	vector<int> common_cols_b;
	vector<int> diff_cols_b;
	this->FindCommAttri(table_b,common_cols_a,common_cols_b,diff_cols_b);
	
	// step 2:
	vector<int> new_query_var_idxes = this->query_var_idxes_;
	for(int i = 0; i < diff_cols_b.size(); i++) 
		new_query_var_idxes.push_back((*table_b->get_query_var_idxes())[diff_cols_b[i]]);
	
	ResultTable* new_table = new ResultTable(new_query_var_idxes);

	// step 3:
	for(int i = 0; i < this->table_data_.size(); i++){
		for(int j = 0; j < table_b->get_table_data()->size(); j++){
			bool row_match = true;
			for(int k = 0; k < common_cols_a.size(); k++){
				if(this->table_data_[i][common_cols_a[k]] != 
					(*table_b->get_table_data())[j][common_cols_b[k]]){
						row_match = false;
						break;
				}
			}
			if(row_match == true){
				vector<int> new_row = this->table_data_[i];
				for(int m = 0; m < diff_cols_b.size(); m++){
					new_row.push_back((*table_b->get_table_data())[j][diff_cols_b[m]]);
				}
				new_table->AddResultTuple(new_row);
			}
			row_match = true;
		}
	}
	return new_table;
}

void ResultTable::FindCommAttri(ResultTable* table_b,vector<int>& comm_cols_a,vector<int>& comm_cols_b,vector<int>& diff_cols_b){
	for(int i = 0; i < table_b->get_query_var_idxes()->size(); i++){
		bool common_indicator = false;
		for(int j = 0; j < this->query_var_idxes_.size(); j++){
			if(this->query_var_idxes_[j] == (*table_b->get_query_var_idxes())[i]){
				comm_cols_a.push_back(j);
				comm_cols_b.push_back(i);
				common_indicator = true;
				break;
			}
		}
		if(common_indicator == false) diff_cols_b.push_back(i);
		common_indicator = false;
	}
}

void ResultTable::SortResultTable(vector<int>& index_to_sort){	
	if(this->table_data_.empty()) return;
	this->sorted_idxes_ = index_to_sort;

	if(this->table_data_.size() > 1000000){
	//if(this->table_data_.size() == 0){
		//cout << "here once" << endl;
		vector<vector<int>> count_container;
		vector<int> col_base(index_to_sort.size());
		int total_count = 1;
		int col_size = 0;
		for(int i = index_to_sort.size() - 1; i >= 0; i--){
			col_base[i] = total_count;
			if(QueryVarTable::GetSize() == 0){
				// for debug purpose
				col_size = 10;
			}else{
			switch(QueryVarTable::GetQueryVarType(this->query_var_idxes_[index_to_sort[i]])){
			case ASSIGN_TYPE:
			case CALL_TYPE:
			case IF_TYPE:
			case WHILE_TYPE:
			case STMT_TYPE:
				col_size = StmtTable::GetSize();
				break;
			case PROC_TYPE:
				col_size = ProcTable::GetSize();
				break;
			case CONST_TYPE:
				col_size = ConstTable::GetSize();
				break;
			case VAR_TYPE:
				col_size = VarTable::GetSize();
				break;
			default:
				cout << "ResultTable sort error" << endl;
				getchar();
				exit(1);
			}
			}
			total_count *= col_size;
		}

		count_container.resize(total_count);

		for(int i = 0; i < this->table_data_.size(); i++){
			int container_index = 0;
			for(int j = 0; j < index_to_sort.size();j++) container_index += ((table_data_[i][index_to_sort[j]] - 1) * col_base[j]);
			count_container[container_index].push_back(i);
		}

		vector<vector<int>> temp_sort(this->table_data_.size());
		int temp_count = 0;
		for(int i = 0; i < count_container.size(); i++){
			int partition_size = count_container[i].size();
			if(partition_size != 0){
				parallel_for_each(concurrency_counter.begin(),concurrency_counter.begin()+partition_size,[&](int it){
					temp_sort[temp_count+it].swap(this->table_data_[count_container[i][it]]);
				});
			}
			temp_count+= partition_size;
		}

		this->table_data_.swap(temp_sort);
	}

	else{
		CompareObject comp_object;
		comp_object.idx_on_sort = &index_to_sort;
		sort(this->table_data_.begin(),this->table_data_.end(),comp_object);
	}

}

ResultTable* ResultTable::ProjectOnCols(vector<int>& cols, bool need_sort){
	if(need_sort) this->SortResultTable(cols);

	// build the projected table
	vector<int> new_query_var_idxes;
	for(int i = 0; i < cols.size(); i++) 
		new_query_var_idxes.push_back(this->query_var_idxes_[cols[i]]);

	ResultTable* proj_table = new ResultTable(new_query_var_idxes);
	proj_table->set_sorted_idxes(cols);

	vector<int> traverser = this->table_data_[0];

	vector<int> tuple;
	for(int i = 0; i < cols.size(); i++) { tuple.push_back(traverser[cols[i]]); }
	proj_table->AddResultTuple(tuple);

	for(int i = 1; i < this->table_data_.size(); i++){
		for(int j = 0; j < cols.size(); j++){
			if(this->table_data_[i][cols[j]] != traverser[cols[j]]){
				traverser = this->table_data_[i];
				for(int k = 0; k < cols.size(); k++){ tuple[k] = traverser[cols[k]]; }
				proj_table->AddResultTuple(tuple);
				break;
			}
		}
	}

	return proj_table;
}

ResultTable* ResultTable::SortMergeJoin(ResultTable* table_b,vector<int>& comm_cols_a,vector<int>& comon_cols_b, vector<int>& diff_cols_b){	
	ResultTable* result_table = NULL;

	int i = 0;
	vector<vector<int>>* left_data = this->get_table_data();
	vector<vector<int>>* right_data = table_b->get_table_data();

	vector<int> new_query_idxes = *(this->get_query_var_idxes());
	for(int i = 0; i < diff_cols_b.size(); i++){
		new_query_idxes.push_back((*table_b->get_query_var_idxes())[diff_cols_b[i]]);
	}

	result_table = new ResultTable(new_query_idxes);

	int right_index_start = 0;

	for(int i = 0; i < left_data->size(); i++){
		int j = right_index_start;
		while(j < right_data->size()){
			int check = CompareTuple((*left_data)[i],(*right_data)[j],comm_cols_a,comon_cols_b);
			if(check == 0){
				vector<int> new_tuple = (*left_data)[i];
				for(int k = 0; k < diff_cols_b.size(); k++) { new_tuple.push_back((*right_data)[j][diff_cols_b[k]]);}
				result_table->AddResultTuple(new_tuple);
			}
			else if(check < 0){
				break;
			}
			else{
				right_index_start++;
			}
			j++;
		}
	}
	
	result_table->set_sorted_idxes(this->sorted_idxes_);
	
	left_data->clear();
	right_data->clear();
	return result_table;
}

ResultTable* ResultTable::CartesianProduct(ResultTable* table_b,bool left_outer_join){
	//cout << "here once" << endl;
	ResultTable* left_table = this;
	ResultTable* right_table = table_b;
	ResultTable* join_table = NULL;

	vector<vector<int>>* left_data = NULL;
	vector<vector<int>>* right_data = NULL;
	vector<int> new_attri_idx;
	
	new_attri_idx = (*left_table->get_query_var_idxes());
	for(int i = 0; i < right_table->get_query_var_idxes()->size(); i++){
		new_attri_idx.push_back((*right_table->get_query_var_idxes())[i]);
	}
	join_table = new ResultTable(new_attri_idx);

	left_data = left_table->get_table_data();
	right_data = right_table->get_table_data();

	
	int size = left_data->size() * right_data->size();
	vector<vector<int>>* join_table_data = join_table->get_table_data();
	join_table_data->resize(size);

	if(left_outer_join){
		int right_size = right_data->size();
		/*********************** normal code **************************/
		for(int i = 0; i < left_data->size(); i++){
			for(int j = 0; j < right_size; j++){
				vector<int> new_tuple = (*left_data)[i];
				new_tuple.insert(new_tuple.end(),(*right_data)[j].begin(),(*right_data)[j].end());
				(*join_table_data)[i*right_size+j].swap(new_tuple);
			}
		}
		/*********************** normal code **************************/

		/*********************** parallel code **************************/
		
		/*if(left_data->size() > concurrency_counter.size()) getchar();
		parallel_for_each(concurrency_counter.begin(),concurrency_counter.begin()+left_data->size(),[&](int i){
			for(int j = 0; j < right_size; j++){
				vector<int> new_tuple = (*left_data)[i];
				new_tuple.insert(new_tuple.end(),(*right_data)[j].begin(),(*right_data)[j].end());
				(*join_table_data)[i*right_size+j].swap(new_tuple);
			}
		});*/
		/*********************** parallel code **************************/

		// set the sorted indexes for the new result table
		vector<int> sorted_new_idx = (*left_table->get_sorted_idxes());
		if(sorted_new_idx.size() == left_data->size()){
			vector<int>* right_sorted_idx = right_table->get_sorted_idxes();
			for(int i = 0; i < right_sorted_idx->size(); i++){
				sorted_new_idx.push_back((*right_sorted_idx)[i]);
			}
			join_table->set_sorted_idxes(sorted_new_idx);
		}
		else{
			join_table->set_sorted_idxes(sorted_new_idx);
		}
	}else{
		int left_size = left_data->size();
		/*********************** normal code **************************/
		for(int i = 0;i < right_data->size(); i++){
			for(int j = 0; j < left_size; j++){
				vector<int> new_tuple = (*left_data)[j];
				new_tuple.insert(new_tuple.end(),(*right_data)[i].begin(),(*right_data)[i].end());
				(*join_table_data)[i*left_size+j].swap(new_tuple);
			}
		}
		/*********************** normal code **************************/

		/*********************** parallel code **************************/
		
		/*if(right_data->size() > concurrency_counter.size()) getchar();
		parallel_for_each(concurrency_counter.begin(),concurrency_counter.begin()+right_data->size(), [&](int i){
			for(int j = 0; j < left_size; j++){
				vector<int> new_tuple = (*left_data)[j];
				new_tuple.insert(new_tuple.end(),(*right_data)[i].begin(),(*right_data)[i].end());
				(*join_table_data)[i*left_size+j].swap(new_tuple);
			}
		});*/
		/*********************** parallel code **************************/

		// set the sorted indexes for the new result table
		vector<int> sorted_new_idx = (*right_table->get_sorted_idxes());
		if(sorted_new_idx.size() == right_data->size()){
			vector<int>* left_sorted_idx = left_table->get_sorted_idxes();
			for(int i = 0; i < left_sorted_idx->size(); i++){
				sorted_new_idx.push_back((*left_sorted_idx)[i]);
			}
			join_table->set_sorted_idxes(sorted_new_idx);
		}
		else{
			join_table->set_sorted_idxes(sorted_new_idx);
		}
	}
	left_data->clear();
	right_data->clear();
	return join_table;
}

bool ResultTable::IsColumnSorted(vector<int> cols_check){
	if(cols_check.size() > this->sorted_idxes_.size()) return false;
	for(int i = 0; i < cols_check.size(); i++){
		if(cols_check[i] != this->sorted_idxes_[i]) return false;
	}
	return true;
}

string ResultTable::ToString(){
	stringstream output;
	output << "the query variable indexes are ";
	for(int i = 0; i < this->query_var_idxes_.size(); i++){
		output << this->query_var_idxes_[i] << " ";
	}
	output << endl;
	
	for(int i = 0; i < this->table_data_.size(); i++){
		for(int j = 0; j < this->table_data_[i].size(); j++){
			switch(QueryVarTable::GetQueryVarType(this->query_var_idxes_[j])){
			case PROC_TYPE:
				output << ProcTable::GetProcName(this->table_data_[i][j]) << " ";
				break;
			case VAR_TYPE:
				output << VarTable::GetVarName(this->table_data_[i][j]) << " ";
				break;
			default:
				output << this->table_data_[i][j] << " ";
			}
			
		}
		output << endl;
	}
	return output.str();
}